import agent
from config import Config
from math import sqrt
from random import random
from copy import deepcopy

class Tile(object):
    UNMAPPED=-1
    NORMAL=0
    MINE=1
    
    def __init__(self, tiletype=0):
        self.tiletype=tiletype

class Map(object):
    def __init__(self, environ, dimensions, number_of_mines):
        self.environ=environ
        self.width, self.height=dimensions
        self._map=[]
        for col in xrange(0, self.width):
            col=[]
            for tile in xrange(0, self.height):
                col.append(Tile(Tile.NORMAL))
            self._map.append(col)
        for i in xrange(0, number_of_mines):
            import random
            while True:
                x=random.randint(0, self.width-1)
                y=random.randint(0, self.height-1)
                if self.get_tile((x,y)).tiletype == Tile.NORMAL:
                    self.set_tile((x,y), Tile(Tile.MINE))
                    break
                
    def get_tile(self, coord):
        '''Gets the tile from the specific (x,y) coordinates'''
        x, y=coord
        try:
            tile=self._map[x][y]
        except:
            tile=Tile()
            self._map[x][y]=tile
        return tile

    def set_tile(self, coord, tile):
        '''Sets the tile from the specific (x,y) coordinates'''
        x, y=coord
        self._map[x][y]=tile
        
    def get_next_tile(self, coords, direction):
        x, y = coords
        if direction == Direction.UP:
            y-=1;
        elif direction == Direction.DOWN:
            y+=1
        elif direction == Direction.LEFT:
            x-=1
        elif direction == Direction.RIGHT:
            x+=1
        elif direction == Direction.DOWNLEFT:
            x-=1
            y+=1
        elif direction == Direction.DOWNRIGHT:
            x+=1
            y+=1
        elif direction == Direction.UPLEFT:
            x-=1
            y-=1
        elif direction == Direction.UPRIGHT:
            x+=1
            y-=1
        else:
            raise Exception("no such direction")
        return (x, y)
    
    def get_direction(self, source, dest):
        dir = 0
        dist = Config.COMMUNICATION_RANGE
        for i in xrange(0, 8):
            coord = self.get_next_tile(source, i)
            if dist > distance(coord, dest):
                dist = distance(coord, dest)
                dir = i
        return dir
        
    def in_map(self, coord):
        x,y=coord
        return 0 <= x <= (self.width-1) and 0 <= y <= (self.height-1)

def distance(pointA, pointB):
    '''Computes Pythagorean distance between two points'''
    return sqrt(abs(pointA[0] - pointB[0])**2 + abs(pointA[1] - pointB[1])**2)

class Environment(object):
    def __init__(self, mapdimensions=(150,150), communication_range=20, communication_reliability=0.95, number_of_mines=150):
        self.envmap=Map(self, mapdimensions, number_of_mines)
        self.comrange=communication_range
        self.agents=[]
        self.mailbox={}
        self.new_mail={}
        self.turn=0
        self.movements=[]
        self.activity=True
        self.com_reliability=communication_reliability

    def add_agent(self, new_agent):
        self.agents.append(new_agent)
        
    def get_agent(self, coords):
        x, y = coords
        for agent in self.agents:
            a_x, a_y = agent.coord
            if a_x == x and a_y == y:
                return agent
        return None

    def next_turn(self):
        if self.activity==False:
            return
        
        self.turn+=1
        self.mailbox=deepcopy(self.new_mail)
        self.new_mail={}
        for a in self.agents[:]:
            a.next_turn()            
        
        for (who, direction) in self.movements:
            prev_coord=who.coord
            x, y = self.envmap.get_next_tile(who.coord, direction)
            if self.envmap.in_map((x,y)):
                who.coord=(x,y)
                log("Agent #%d moved from %s to (%d, %d)" % (who.ID, prev_coord, x,y))
                self.check_location(who)
            else:
                log("Agent #%d tried to move to an invalid location %s" % (who.ID, (x,y)))
                who.coord=prev_coord

        # clear the movements
        self.movements=[]
        
    def check_location(self, agent):
        '''Checks the current location of agent
        
        Removes agent if it was exploded
        '''
        
        if Tile.MINE == self.envmap.get_tile(agent.coord).tiletype:
            log("Agent #%d was exploded by mine in %s" % (agent.ID, agent.coord))
            self.agents.remove(agent)
            if len(self.agents) == 0:
                log("All agents were exploded. Game over.")
                exit()
                
    def check_locations(self):
        for agent in self.agents[:]:
            self.check_location(agent)

    def sense_nearby_agents(self, source):
        '''Returns a list of agents in communication range

	The returned list contains (agent, distance) tuples
	'''
        
        assert isinstance(source, agent.Agent)

        nearby_agents=[]
        for a in self.agents:
            dst=distance(source.coord, a.coord)
            if source != a and dst <= self.comrange:
                nearby_agents.append((a, dst))

        return nearby_agents

    def send_message(self, source_id, destination_id, msgtype, message):
        if not self.new_mail.has_key(destination_id):
            self.new_mail[destination_id]=[]

        if random()<self.com_reliability:
            self.new_mail[destination_id].append((source_id, msgtype, message))
            log("Agent #%d sent a message to agent #%d with type %s and message %s"  % (source_id, destination_id, msgtype, message))
        else:
            log("Agent #%d tried to sent a message to agent #%d, which has been lost with type %s and message %s" % (source_id, destination_id, msgtype, message))

    def receive_message(self, destination_id):
        lst=self.mailbox.get(destination_id)
        if lst:            
            return lst.pop()
        else:
            return None
    
    def move(self, who, direction):
        self.movements.append((who, direction))
        
    def set_activity(self, activity):
        '''activity - must be a boolean (False means pause)'''
        self.activity=activity


class Direction:
    UP=0
    DOWN=1
    LEFT=2
    RIGHT=3
    UPLEFT=4
    UPRIGHT=5
    DOWNLEFT=6
    DOWNRIGHT=7
    
    def opposite(self, d):
        opposites = [1, 0, 3, 2, 7, 6, 5, 4]
        return opposites[d]
    

def log(msg):
    print msg
    
# it's a pure module:		
if __name__ == "__main__":
    pass
